﻿using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks.Dataflow;
using NLog;
using QuantBox.XApi;
using SmartQuant;
using ExecType = SmartQuant.ExecType;
using OrderStatus = SmartQuant.OrderStatus;

namespace QuantBox
{
    internal class DealProcessor
    {
        private readonly struct TradingEvent
        {
            public readonly ExecType Type;
            public readonly Order? Order;
            public readonly TradeField? Trade;
            public readonly OrderField? OrderReturn;

            public TradingEvent(ExecType type, Order order)
            {
                Type = type;
                Order = order;
                Trade = null;
                OrderReturn = null;
            }

            public TradingEvent(ExecType type, TradeField trade)
            {
                Type = type;
                Trade = trade;
                Order = null;
                OrderReturn = null;
            }

            public TradingEvent(ExecType type, OrderField order)
            {
                Type = type;
                OrderReturn = order;
                Trade = null;
                Order = null;
            }
        }

        private delegate void OrderReturnHandler(OrderField field, OrderRecord record);

        private readonly XProvider _provider;
        private readonly OrderMap _orderMap = new();
        private readonly HashSet<string> _tradeSet = new();
        private readonly IdArray<OrderReturnHandler> _orderHandlers = new(byte.MaxValue);
        private Logger _logger;
        private ActionBlock<TradingEvent>? _orderAction;

        private OrderField CreateOrderField(Order order, string localId, string orderId = "")
        {
            var field = new OrderField();
            var (symbol, exchange) = _provider.GetSymbolInfo(order.Instrument);
            field.InstrumentID = symbol;
            field.ExchangeID = exchange;
            field.Price = order.Price;
            field.Qty = order.Qty;
            field.TimeInForce = (XApi.TimeInForce)order.TimeInForce;
            field.Type = (XApi.OrderType)order.Type;
            field.HedgeFlag = Convertor.GetHedgeFlag(order, _provider.DefaultHedgeFlag);
            field.Side = Convertor.GetSide(order);
            field.OpenClose = order.GetOpenClose();
            if (field.OpenClose == OpenCloseType.Open
                && field.Side == XApi.OrderSide.Sell
                && order.SubSide == SubSide.Undefined)
            {
                order.SetSubSide(SubSide.SellShort);
            }
            field.ClientID = order.Account;
            field.AccountID = order.Account;
            field.StopPx = order.StopPx;
            field.ID = GetOrderId(order);
            field.LocalID = localId;
            if (!string.IsNullOrEmpty(orderId))
            {
                field.OrderID = orderId;
                field.Status = Convertor.GetStatus(order.Status);
            }
            else
            {
                field.Status = XApi.OrderStatus.NotSent;
            }

            if (!string.IsNullOrEmpty(order.Text))
            {
                field.SetText(order.Text);
            }
            return field;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static string GetOrderId(Order order)
        {
            return order.ClOrderId;
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        internal static (string localId, string orderId) GetProviderOrderId(Order order)
        {
            var id = order.ProviderOrderId;
            if (id.Length < XProvider.LocalIdLength)
            {
                return (id, string.Empty);
            }
            return id.Length == XProvider.LocalIdLength
                ? (id, string.Empty)
                : (id.Substring(0, XProvider.LocalIdLength), id.Substring(XProvider.LocalIdLength + 1));
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static string ShowTrade(TradeField trade)
        {
            var line = new StringBuilder();
            line.AppendFormat("id:{0}", trade.ID).Append(",");
            line.AppendFormat("tid:{0}", trade.TradeID).Append(",");
            line.AppendFormat("oid:{0}", trade.ClientID).Append(",");
            line.Append(trade.InstrumentID).Append(",");
            line.Append(trade.Side).Append(",");
            line.Append(trade.OpenClose).Append(",");
            line.AppendFormat("p:{0}", trade.Price).Append(",");
            line.AppendFormat("v:{0}", trade.Qty);
            return line.ToString();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static string ShowOrderStatus(OrderField order)
        {
            var line = new StringBuilder();
            line.AppendFormat("id:{0}", order.ID).Append(",");
            line.AppendFormat("ref:{0}", order.LocalID).Append(",");
            line.AppendFormat("oid:{0}", order.OrderID).Append(",");
            line.Append(order.Status);
            return line.ToString();
        }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        private static string ShowOrder(OrderField order)
        {
            var line = new StringBuilder();
            line.AppendFormat("id:{0}", order.ID).Append(",");
            line.AppendFormat("ref:{0}", order.LocalID).Append(",");
            line.AppendFormat("oid:{0}", order.OrderID).Append(",");
            line.Append(order.InstrumentID).Append(",");
            line.Append(order.Side).Append(",");
            line.Append(order.OpenClose).Append(",");
            line.Append(order.Status).Append(",");
            line.Append($"type:{order.ExecType}").Append(",");
            line.AppendFormat("p:{0}", order.Price).Append(",");
            line.AppendFormat("v:{0}", order.Qty);
            return line.ToString();
        }

        private static ExecutionReport CreateReport(OrderRecord record, OrderStatus ordStatus, ExecType execType, string text = "")
        {
            var report = new ExecutionReport(record.Order)
            {
                DateTime = DateTime.Now,
                AvgPx = record.AvgPx,
                CumQty = record.CumQty,
                LeavesQty = record.LeavesQty,
                OrdStatus = ordStatus,
                ExecType = execType,
                Text = string.IsNullOrEmpty(text) ? record.Order.Text : text
            };
            return report;
        }

        #region ProcessOrderReturn

        private void InitHandler()
        {
            static void DefaultHandler(OrderField field, OrderRecord record)
            {
            }

            for (var i = 0; i < byte.MaxValue; i++)
            {
                _orderHandlers[i] = DefaultHandler;
            }
            _orderHandlers[(byte)XApi.ExecType.New] = ProcessExecNew;
            _orderHandlers[(byte)XApi.ExecType.Cancelled] = ProcessExecCancelled;
            _orderHandlers[(byte)XApi.ExecType.Rejected] = ProcessExecRejected;
            _orderHandlers[(byte)XApi.ExecType.PendingCancel] = ProcessExecPendingCancel;
            _orderHandlers[(byte)XApi.ExecType.CancelReject] = ProcessExecCancelReject;
        }

        private void ProcessReturnOrder(OrderField field)
        {
            if (!_orderMap.TryGetOrder(field.ID, out var record))
            {
                _logger.Warn($"外部委托,{ShowOrderStatus(field)}");
                return;
            }

            _logger.Debug($"委托回报,{ShowOrder(field)}");
            switch (field.Status)
            {
                case XApi.OrderStatus.New:
                    if (!string.IsNullOrEmpty(record.OrderId))
                    {
                        return;
                    }
                    break;
                case XApi.OrderStatus.Cancelled:
                case XApi.OrderStatus.Expired:
                case XApi.OrderStatus.Rejected:
                case XApi.OrderStatus.Replaced:
                    break;
                default:
                    return;
            }
            _orderHandlers[(byte)field.ExecType](field, record);
            record.OrderStatus = field.Status;
        }

        private void ProcessExecCancelled(OrderField field, OrderRecord record)
        {
            _orderMap.RemoveDone(field.ID);
            _provider.OnMessage(CreateReport(record, (OrderStatus)field.Status, ExecType.ExecCancelled));
            _logger.Debug($"委托撤销完成,id:{field.ID},ref:{field.LocalID}");
        }

        private void ProcessExecNew(OrderField field, OrderRecord record)
        {
            _orderMap.RemoveNoSent(field.ID);
            var (localId, orderId) = GetProviderOrderId(record.Order);
            if (!string.IsNullOrEmpty(orderId))
            {
                _logger.Warn($"委托交易所编码已存在,{ShowOrderStatus(field)}");
                return;
            }
            record.OrderId = field.OrderID;
            record.Order.ProviderOrderId = $"{localId}_{field.OrderID}";
            _provider.OnMessage(CreateReport(record, (OrderStatus)field.Status, ExecType.ExecNew));
        }

        private void ProcessExecRejected(OrderField field, OrderRecord record)
        {
            var report = CreateReport(record, (OrderStatus)field.Status, ExecType.ExecRejected, field.Text());
            report.SetErrorId(field.XErrorID, field.RawErrorID);
            _provider.OnMessage(report);
            _orderMap.RemoveDone(field.ID);
            _orderMap.RemoveNoSent(field.ID);
        }

        private void ProcessExecPendingCancel(OrderField field, OrderRecord record)
        {
            _provider.OnMessage(CreateReport(record, (OrderStatus)field.Status, ExecType.ExecPendingCancel));
        }

        private void ProcessExecCancelReject(OrderField field, OrderRecord record)
        {
            _provider.OnMessage(CreateReport(record, (OrderStatus)field.Status, ExecType.ExecCancelReject));
        }

        private void ProcessTrade(TradeField trade)
        {
            if (_tradeSet.Contains(trade.TradeID))
            {
                _logger.Warn($"重复的成交,id:{trade.ID},tid:{trade.TradeID},oid:{trade.ClientID}");
                return;
            }

            _tradeSet.Add(trade.TradeID);
            if (!_orderMap.TryGetOrder(trade.ID, out var record))
            {
                _logger.Warn($"外部成交,id:{trade.ID},tid:{trade.TradeID},oid:{trade.ClientID}");
                return;
            }
            _logger.Debug($"委托成交,{ShowTrade(trade)}");
            if (record.OrderId != trade.ClientID)
            {
                record.Order.ProviderOrderId = $"{record.LocalId}_{trade.ClientID}";
            }

            record.AddFill(trade.Price, trade.Qty);
            var status = record.LeavesQty > 0 ? OrderStatus.PartiallyFilled : OrderStatus.Filled;
            var report = CreateReport(record, status, ExecType.ExecTrade);
            report.ExecId = trade.TradeID;
            report.DateTime = trade.UpdateTime();
            report.TransactTime = report.DateTime.Date != _provider.trader!.TradingDay
                ? _provider.trader.TradingDay.Add(report.DateTime.TimeOfDay)
                : report.DateTime;

            report.LastPx = trade.Price;
            report.LastQty = trade.Qty;
            report.Commission = Math.Abs(trade.Commission) < double.Epsilon
                ? _provider.GetCommission(report)
                : trade.Commission;

            _provider.OnMessage(report);
            if (status == OrderStatus.Filled)
            {
                _orderMap.RemoveDone(trade.ID);
                _logger.Debug($"委托完成,id:{trade.ID},tid:{trade.TradeID},oid:{trade.ClientID}");
            }
        }

        #endregion

        #region ProcessCommand

        private void OrderEventAction(TradingEvent e)
        {
            try
            {
                switch (e.Type)
                {
                    case ExecType.ExecNew:
                        ProcessSend(e.Order!);
                        break;
                    case ExecType.ExecTrade:
                        ProcessTrade(e.Trade!);
                        break;
                    case ExecType.ExecCancelled:
                        ProcessCancel(e.Order!);
                        break;
                    case ExecType.ExecOrderStatus:
                        ProcessReturnOrder(e.OrderReturn!);
                        break;
                }
            }
            catch (Exception ex)
            {
                _logger.Error(ex);
                _provider.OnProviderError(-1, ex.Message);
            }
        }

        private void ProcessCancel(Order order)
        {
            var id = GetOrderId(order);
            _logger.Debug($"委托撤销,id:{id},ref:{order.ProviderOrderId},text:{order.Text}");

            var error = _orderMap.OrderExist(id)
                ? _provider.trader!.CancelOrder(id)
                : "撤销的订单在插件中未注册";

            if (string.IsNullOrEmpty(error))
                return;

            _logger.Warn($"委托撤销失败,{error}");
            _provider.OnMessage(CreateReport(
                new OrderRecord(order),
                order.Status,
                ExecType.ExecCancelReject,
                error));
        }

        private void ProcessSend(Order order)
        {
            var id = GetOrderId(order);
            var (localId, orderId) = GetProviderOrderId(order);
            _orderMap.AddNewOrder(id, orderId, order);
            var field = CreateOrderField(order, localId, orderId);
            _logger.Debug($"委托发送,{ShowOrder(field)},text:{order.Text}");
            _provider.trader!.SendOrder(field);
        }

        #endregion

        public DealProcessor(XProvider provider)
        {
            _provider = provider;
            _logger = _provider.logger;
            InitHandler();
        }

        public void Init()
        {
            _logger = _provider.logger;
            _orderAction ??= new ActionBlock<TradingEvent>(OrderEventAction);
        }

        public void Close()
        {
            if (_orderAction != null)
            {
                _orderAction.Complete();
                _orderAction.Completion.Wait();
                _orderAction = null;
            }

            Clear();
        }

        public void Clear()
        {
            _orderMap.Clear();
            _tradeSet.Clear();
        }

        public void PostNewOrder(Order order)
        {
            _orderAction!.Post(new TradingEvent(ExecType.ExecNew, order));
        }

        public void PostCancelOrder(Order order)
        {
            _orderAction!.Post(new TradingEvent(ExecType.ExecCancelled, order));
        }

        public void PostTrade(TradeField trade)
        {
            _orderAction!.Post(new TradingEvent(ExecType.ExecTrade, trade));
        }

        public void PostReturnOrder(OrderField order)
        {
            _orderAction!.Post(new TradingEvent(ExecType.ExecOrderStatus, order));
        }
    }
}
